#pragma once

#include "Animation_Task.h"
#include "Animation_TaskSerializer.h"
#include "Engine/Animation/AnimationBoneMask.h"

//-------------------------------------------------------------------------

namespace EE::TypeSystem { class TypeRegistry; }

//-------------------------------------------------------------------------

namespace EE::Animation
{
    #if EE_DEVELOPMENT_TOOLS
    enum class TaskSystemDebugMode
    {
        Off,
        FinalPose,
        PoseTree,
        DetailedPoseTree
    };
    #endif

    //-------------------------------------------------------------------------

    class TaskSerializer;

    //-------------------------------------------------------------------------

    class EE_ENGINE_API TaskSystem
    {
        friend class AnimationDebugView;

    public:

        TaskSystem( Skeleton const* pSkeleton );
        ~TaskSystem();

        void Reset();

        // Get the actual final character transform for this frame
        Transform const& GetCharacterWorldTransform() const { return m_taskContext.m_worldTransform; }

        // Pose(s)
        //-------------------------------------------------------------------------

        // Get final pose buffer
        PoseBuffer const* GetPoseBuffer() const { return &m_finalPoseBuffer; }

        // Get the final primary pose generated by the task system
        Pose const* GetPrimaryPose() const{ return &m_finalPoseBuffer.m_poses[0]; }

        // Get the final primary pose generated by the task system
        TInlineVector<Pose const*, 1> GetSecondaryPoses() const;

        // Skeleton(s)
        //-------------------------------------------------------------------------

        // Get the primary skeleton from this task system
        Skeleton const* GetSkeleton() const { return m_posePool.GetPrimarySkeleton(); }

        // Set the skeleton LOD for the result pose
        EE_FORCE_INLINE void SetSkeletonLOD( Skeleton::LOD lod ) { m_taskContext.m_skeletonLOD = lod; }

        // Get the current skeleton LOD we are using
        EE_FORCE_INLINE Skeleton::LOD GetSkeletonLOD() const { return m_taskContext.m_skeletonLOD; }

        // Set the secondary skeletons that we can animate
        void SetSecondarySkeletons( SecondarySkeletonList const& secondarySkeletons );

        // Get the number of secondary skeletons set
        EE_FORCE_INLINE int32_t GetNumSecondarySkeletons() const { return m_posePool.GetNumSecondarySkeletons(); }

        // Execution
        //-------------------------------------------------------------------------

        // Do we have any tasks that need execution
        inline bool RequiresUpdate() const { return m_needsUpdate; }

        // Do we have a physics dependency in our registered tasks
        inline bool HasPhysicsDependency() const { return m_hasPhysicsDependency; }

        // Run all pre-physics tasks
        void UpdatePrePhysics( float deltaTime, Transform const& worldTransform, Transform const& worldTransformInverse );

        // Run all post-physics tasks and fill out the final pose buffer
        void UpdatePostPhysics();

        // Cached Pose storage
        //-------------------------------------------------------------------------

        inline UUID CreateCachedPose() { return m_posePool.CreateCachedPoseBuffer(); }
        inline void ResetCachedPose( UUID const& cachedPoseID ) { return m_posePool.ResetCachedPoseBuffer( cachedPoseID ); }
        inline void DestroyCachedPose( UUID const& cachedPoseID ) { m_posePool.DestroyCachedPoseBuffer( cachedPoseID ); }

        // Task Registration
        //-------------------------------------------------------------------------

        inline bool HasTasks() const { return m_tasks.size() > 0; }
        inline TVector<Task*> const& GetRegisteredTasks() const { return m_tasks; }

        template< typename T, typename ... ConstructorParams >
        inline TaskIndex RegisterTask( ConstructorParams&&... params )
        {
            EE_ASSERT( m_tasks.size() < 0xFF );
            auto pNewTask = m_tasks.emplace_back( EE::New<T>( eastl::forward<ConstructorParams>( params )... ) );
            m_hasPhysicsDependency |= pNewTask->HasPhysicsDependency();
            m_needsUpdate = true;
            return (TaskIndex) ( m_tasks.size() - 1 );
        }

        TaskIndex GetCurrentTaskIndexMarker() const { return (TaskIndex) m_tasks.size(); }
        void RollbackToTaskIndexMarker( TaskIndex const marker );

        // Task Serialization
        //-------------------------------------------------------------------------

        // Have we enabled serialization
        bool IsSerializationEnabled() const { return m_serializationEnabled; }

        // Enable serialization
        void EnableSerialization( TypeSystem::TypeRegistry const& typeRegistry );

        // Disable Serialization
        void DisableSerialization();

        // Serialized the current executed tasks - NOTE: this can fail since some tasks (i.e. physics) cannot be serialized!
        // Only do this if there are no currently pending tasks!
        bool SerializeTasks( TInlineVector<ResourceLUT const*, 10> const& LUTs, Blob& outSerializedData ) const;

        // Create a new set of tasks from a serialized set of data
        // Only do this if there are no registered tasks!
        void DeserializeTasks( TInlineVector<ResourceLUT const*, 10> const& LUTs, Blob const& inSerializedData );

        // Debug
        //-------------------------------------------------------------------------

        #if EE_DEVELOPMENT_TOOLS
        void SetDebugMode( TaskSystemDebugMode mode );
        TaskSystemDebugMode GetDebugMode() const { return m_debugMode; }
        void DrawDebug( Drawing::DrawContext& drawingContext );
        #endif

    private:

        bool AddTaskChainToPrePhysicsList( TaskIndex taskIdx );
        void ExecuteTasks();

    private:

        TVector<Task*>                          m_tasks;
        PoseBufferPool                          m_posePool;
        BoneMaskPool                            m_boneMaskPool;
        TaskContext                             m_taskContext;
        TInlineVector<TaskIndex, 16>            m_prePhysicsTaskIndices;
        PoseBuffer                              m_finalPoseBuffer;
        bool                                    m_hasPhysicsDependency = false;
        bool                                    m_hasCodependentPhysicsTasks = false;
        bool                                    m_needsUpdate = false;

        //-------------------------------------------------------------------------

        TVector<TypeSystem::TypeInfo const*>    m_taskTypeRemapTable;
        uint32_t                                m_maxBitsForTaskTypeID = 0;
        bool                                    m_serializationEnabled = false;

        //-------------------------------------------------------------------------

        #if EE_DEVELOPMENT_TOOLS
        TaskSystemDebugMode                     m_debugMode = TaskSystemDebugMode::Off;
        #endif
    };
}